//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using OpenLS.Spreadsheet.Expressions;
using System.Diagnostics.Contracts;

namespace OpenLS.Spreadsheet
{
    internal class WorkbookResolver
    {
        private readonly List<SimpleAction> _resolveActions = new List<SimpleAction>();
        private readonly Workbook _workbook;
        private readonly bool _equalAlreadyIncluded;

        private readonly List<Pair<Cell, string>> _formulas = new List<Pair<Cell, string>>();
        private readonly List<Pair<Cell, SharedFormulaKey>> _sharedFormulaRef = new List<Pair<Cell, SharedFormulaKey>>();

        private readonly Dictionary<SharedFormulaKey, TempSharedFormula> _sharedFormulas =
            new Dictionary<SharedFormulaKey, TempSharedFormula>();

        readonly List<TempArrayFormula> _arrayFormulas = new List<TempArrayFormula>();

        public WorkbookResolver(Workbook workbook, bool equalAlreadyIncluded)
        {
            _workbook = workbook;
            _equalAlreadyIncluded = equalAlreadyIncluded;
        }

        internal List<SimpleAction> ResolveActions
        {
            get { return _resolveActions; }
        }

        private List<Pair<Cell, string>> Formulas
        {
            get { return _formulas; }
        }

        private void setFormulas()
        {
            foreach (var pair in _formulas)
            {
                string formula = pair.Second;
                if (!_equalAlreadyIncluded)
                    formula = "=" + formula;
                pair.First.SetFormula(formula, false);
            }
            foreach (var f in _sharedFormulas.Values)
                f.Resolve();
#if DEBUG
            int counter = 0;
#endif
            foreach (var pair in _sharedFormulaRef)
            {
                //\\ f.Resolve();
                var tempFormula = _sharedFormulas[pair.Second];
                SharedExpression target = tempFormula.Expression;
                //\\pair.First.Expression = target.First.Expression.Offset(new CellRelativeAddress(pair.First.Address, target.First.Address));
#if DEBUG
                counter++;
                if (counter%100 == 0)
                {
                    //Debug.WriteLine(string.Format("{0} / {1}", counter, _sharedFormulaRef.Count));
                }
#endif
                if (target != null)
                    pair.First.SetExpressionRecalc(new SharedExpressionReference(target, pair.First), false);
                else
                {
                    pair.First.Value = tempFormula.Target.Value;
                }
            }
            foreach (var arrayFormula in _arrayFormulas)
            {
                var arrayRange = arrayFormula.Range;
                arrayRange.SetArrayFormula("="+arrayFormula.Formula, false);
            }
        }


        internal void AddFormula(Cell cell, string formula)
        {
            Contract.Requires(!string.IsNullOrEmpty(formula));
            var pair = new Pair<Cell, string> {First = cell, Second = formula};
            Formulas.Add(pair);
        }

        internal void AddSharedFormula(Cell cell, string rangeText, string formula, int id)
        {
            Contract.Requires(!string.IsNullOrEmpty(formula));
            SharedFormulaKey k = SharedFormulaKey.Create(cell.Worksheet, id);
            _sharedFormulas[k] = new TempSharedFormula(cell, rangeText, formula);
        }

        internal void AddSharedFormulaRef(Cell cell, int id)
        {
            SharedFormulaKey k = SharedFormulaKey.Create(cell.Worksheet, id);
            _sharedFormulaRef.Add(new Pair<Cell, SharedFormulaKey>(cell, k));
        }

        //\\Dictionary<int, Pair<Cell, string>> _sharedFormulas = new Dictionary<int, Pair<Cell, string>>(); 

        internal void ResolveAll()
        {
            foreach (var worksheet in Enumerable.OfType<Worksheet>(_workbook.Sheets))
            {
                worksheet.IsInitializing = true; 
            }

            setFormulas();
            foreach (var worksheet in Enumerable.OfType<Worksheet>(_workbook.Sheets))
            {
                worksheet.IsInitializing = false;
            }
            foreach (SimpleAction d in ResolveActions)
            {
                d();
            }
        }

        #region Nested type: SharedFormulaKey

        private struct SharedFormulaKey
        {
            private int id;
            private Worksheet worksheet;

            internal static SharedFormulaKey Create(Worksheet ws, int id)
            {
                var result = new SharedFormulaKey {id = id, worksheet = ws};
                return result;
            }

            public override int GetHashCode()
            {
                return worksheet.GetHashCode() + id.GetHashCode();
            }

            public override bool Equals(object obj)
            {
                if (obj is SharedFormulaKey)
                {
                    return Equals((SharedFormulaKey) obj);
                }
                return false;
            }

            public bool Equals(SharedFormulaKey k)
            {
                return id == k.id && worksheet == k.worksheet;
            }
        }

        #endregion

        #region Nested type: TempFormula
        class TempArrayFormula: TempFormula
        {
            private Expression _expression;
            private readonly string _formula;

            public TempArrayFormula(Cell cell, string rangeLabel, string text) : base(cell, rangeLabel, text)
            {
                this._formula = text;
            }

            public string Formula
            {
                get {
                    return _formula;
                }
            }
        }
        class TempSharedFormula: TempFormula
        {
            public TempSharedFormula(Cell cell, string rangeLabel, string text) : base(cell, rangeLabel, text)
            {
            }
            public SharedExpression Expression;
            public void Resolve()
            {
                if (Expression == null)
                {
                    _target.SetFormula("=" + _text, false);
                    if (_target.Expression != null)
                    {
                        Expression = new SharedExpression(RangeAddress, _target.Expression.Offset(RangeAddress.TopLeft - _target.Address));
                        _target.SetExpressionRecalc(new SharedExpressionReference(Expression, _target), false);
                    }
                }
            }

        }
        private abstract class TempFormula
        {
            private readonly string _rangeLabel;
            protected readonly Cell _target;
            protected readonly string _text;

            protected TempFormula(Cell cell, string rangeLabel, string text)
            {
                Contract.Requires(cell != null);
                _target = cell;
                _rangeLabel = rangeLabel;
                _text = text;
            }

            public Range Range
            {
                get
                {
                    Range r = _target.Worksheet.GetRange(_rangeLabel);
                    if (r == null)
                        r = _target.Worksheet.GetRange(_rangeLabel);
                    return r;
                }
            }

            protected RangeAddress RangeAddress
            {
                get
                {
                    Range r = _target.Worksheet.GetRange(_rangeLabel);
                    if (r == null)
                        r = _target.Worksheet.GetRange(_rangeLabel);
                    return r.Address;
                }
            }

            public Cell Target
            {
                get { return _target; }
            }

        }

        #endregion

        public void AddArrayFormula(Cell cell, string reference, string text, int si)
        {
            var formula = new TempArrayFormula(cell, reference, text);
            _arrayFormulas.Add(formula);
        }
    }
}